package com.aoplog.demo.aop;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson.JSON;
import com.aoplog.demo.annotation.CustomerAnnotationLog;
import com.aoplog.demo.entity.LogMessage;
import com.aoplog.demo.entity.Person;
import com.aoplog.demo.enums.LogScopeEnum;
import com.aoplog.demo.service.PersonService;
import com.aoplog.demo.util.DateUtil;
import com.aoplog.demo.util.HttpUtils;
import org.apache.ibatis.javassist.ClassClassPath;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.javassist.bytecode.LocalVariableAttribute;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * @author zcx
 * @Title 日志记录AOP实现
 * @date 2019年01月24日 11:45
 **/
@Aspect
@Component
public class WebLogAspect {

    private static final org.slf4j.Logger LOGGER =LoggerFactory.getLogger(WebLogAspect.class);

    //开始时间
    private Long startTime = 0L;

    //结束时间
    private Long endTime = 0L;

    @Autowired
    private PersonService personService;
    /**
    *此处的Controller层切点是注解的方式，也可以用包名的方式达到相同的效果
     * '@Pointcut("execution(* com.aoplog.demo.service.*.*(..))")'
     * @author zcx
     * @date 2019/1/24 14:00
     * @param 
     * @return void
     */
    @Pointcut("@annotation(com.aoplog.demo.annotation.CustomerAnnotationLog)")
    public void controllerAspect() {

    }

    /**
    *前置通知 用于拦截Controller层记录用户的操作
     * @author zcx
     * @date 2019/1/24 14:04
     * @param joinPoint 切点
     * @return void
     */
    @Before("controllerAspect()")
    public void doBeforeAdvice(JoinPoint joinPoint) {

    }

    /**
    *配置controller环绕通知,使用在方法aspect()上注册的切入点
     * @author zcx
     * @date 2019/1/24 14:06
     * @param point 切点
     * @return java.lang.Object
     */
    @Around("controllerAspect()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        // 获取request
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        HttpServletRequest request = servletRequestAttributes.getRequest();
        //目标方法实体
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        boolean hasMethodLogAnno = method.isAnnotationPresent(CustomerAnnotationLog.class);
        //没加注解 直接执行返回结果
        if (!hasMethodLogAnno) {
            return point.proceed();
        }
        //记录日志信息
        LogMessage logMessage = new LogMessage();
        //方法注解实体
        CustomerAnnotationLog methodLogAnnon = method.getAnnotation(CustomerAnnotationLog.class);
        //处理入参日志
        handleRequstLog(point, methodLogAnnon, request, logMessage);
        //执行目标方法内容，获取执行结果
        Object result = point.proceed();
        //处理接口响应日志
        handleResponseLog(point,logMessage, methodLogAnnon, result,request);
        return result;
    }


    /**
    *处理入参日志
     * @author zcx
     * @date 2019/1/24 14:14
     * @param point 切点
     * @param methodLogAnnon 日志注解
     * @param request
     * @param logMessage 日志信息记录实体
     * @return void
     */
    private void handleRequstLog(ProceedingJoinPoint point, CustomerAnnotationLog methodLogAnnon,
                                 HttpServletRequest request, LogMessage logMessage) throws Exception{
        String paramsText = "";
        //参数列表
        String includeParam = methodLogAnnon.include();
        Map<String, Object> methodParamNames = getMethodParamNames(
                        point.getTarget().getClass(), point.getSignature().getName(), includeParam);
        Map<String, Object> params = getArgsMap(point, methodParamNames);
//        List<Object> params = getArgsList(point, methodParamNames);
        if (params != null) {
            //序列化参数列表
            paramsText = JSON.toJSONString(params);
        }
        logMessage.setArgs(paramsText);
        //判断是否输出日志
        if (methodLogAnnon.isPrintLog()
                && methodLogAnnon.scope().contains(LogScopeEnum.BEFORE)
                && methodLogAnnon.console()) {
            //打印入参日志
            LOGGER.info("【{}】 接口入参成功!, 方法名称:【{}】, 请求参数:【{}】", methodLogAnnon.descp().toString()
                    , point.getSignature().getName(), paramsText);
        }
        startTime = System.currentTimeMillis();
    }


    /**
    * 处理响应日志
     * @author zcx
     * @date 2019/1/24 14:36
     * @param point
     * @param logMessage 日志记录信息实体
     * @param methodLogAnnon 日志注解实体
     * @param result 接口执行结果
     * @return void
     */
    private void handleResponseLog(ProceedingJoinPoint point, LogMessage logMessage, CustomerAnnotationLog methodLogAnnon, Object result, HttpServletRequest request) throws Exception {
        endTime = System.currentTimeMillis();
        //结束时间
        logMessage.setEndTime(DateUtil.getNowDate());
        //消耗时间
        logMessage.setRunTime(endTime - startTime);
        //是否输出日志
        if (methodLogAnnon.isPrintLog()
                && methodLogAnnon.scope().contains(LogScopeEnum.AFTER)) {
            //判断是否入库
            if (methodLogAnnon.writeDb()) {
                Person person = new Person();
                person.setId("10");
                person.setAge(29);
                person.setName("btr");
                person.setAddress("FnynhZ");
                personService.insert(person);
            }
            //判断是否输出到控制台
            if (methodLogAnnon.console()) {
                logMessage.setDescribe(methodLogAnnon.descp());
                logMessage.setMethod(point.getSignature().getName());
                //获取请求参数方式
                logMessage.setRequestName(request.getMethod());
                logMessage.setReturnValue(JSON.toJSONString(result));
                //获取请求的接口名
                logMessage.setUri(request.getRequestURI());
                //获取客户端请求的ip地址
                logMessage.setIp(HttpUtils.getIpAddress());
                //获取完整的请求url地址
                logMessage.setUrl(request.getRequestURL().toString());
                logMessage.setBasePath(HttpUtils.getLocalIP());
                LOGGER.info(logMessage.format(),logMessage.args());
            }
        }
    }

    /**
    *获取方法入参变量名
     * @author zcx
     * @date 2019/1/24 14:32
     * @param aClass 触发的类
     * @param name 触发的方法名
     * @param includeParam 需要打印的变量名
     * @return java.util.Map<java.lang.String,java.lang.Object>
     */
    private Map<String,Object> getMethodParamNames(Class aClass, String name, String includeParam) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(aClass));
        CtMethod cm = pool.get(aClass.getName()).getDeclaredMethod(name);
        LocalVariableAttribute attr = (LocalVariableAttribute) cm
                .getMethodInfo().getCodeAttribute()
                .getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            throw new Exception("attr is null");
        } else {
            Map<String, Object> paramNames = new HashMap<>();
            int paramNamesLen = cm.getParameterTypes().length;
            int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
            if (StringUtils.isEmpty(includeParam)) {
                for (int i = 0; i < paramNamesLen; i++) {
                    paramNames.put(attr.variableName(i + pos), i);
                }
            } else { // 若include不为空
                for (int i = 0; i < paramNamesLen; i++) {
                    String paramName = attr.variableName(i + pos);
                    if (includeParam.indexOf(paramName) > -1) {
                        paramNames.put(paramName, i);
                    }
                }
            }
            return paramNames;
        }
    }

    /*
    *组装入参Map
     * @author zcx
     * @date 2019/1/24 14:33
     * @param point 切点
     * @param methodParamNames 参数名称集合
     * @return java.util.Map<java.lang.String,java.lang.Object>
     */
    private Map<String, Object> getArgsMap(ProceedingJoinPoint point, Map<String, Object> methodParamNames) {
        Object[] args = point.getArgs();
        if (null == methodParamNames) {
            return Collections.EMPTY_MAP;
        }
        for (Map.Entry<String, Object> entry : methodParamNames.entrySet()) {
            int index = Integer.valueOf(String.valueOf(entry.getValue()));
            if (args != null && args.length > 0) {
                Object arg = (null == args[index] ? "" : args[index]);
                methodParamNames.put(entry.getKey(), arg);
            }
        }
        return methodParamNames;
    }

    private List<Object> getArgsList(ProceedingJoinPoint point, Map<String, Object> methodParamNames) {
        Object[] args = point.getArgs();
        List<Object> objects = new ArrayList<>();
        if (null == methodParamNames) {
            return Collections.emptyList();
        }
        for (Map.Entry<String, Object> entry : methodParamNames.entrySet()) {
            int index = Integer.valueOf(String.valueOf(entry.getValue()));
            if (args != null && args.length > 0) {
                Object arg = (null == args[index] ? "" : args[index]);
                objects.add(arg);
            }
        }
        return objects;
    }
}
